package com.liming.common.excel;

import cn.hutool.core.util.ObjectUtil;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.write.metadata.WriteSheet;
import com.liming.common.exception.BusinessException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author liming
 * @date 2025/4/29
 */
@Slf4j
public final class ExcelUtil {

    /**
     * 默认读取，可以自定义监听器，实现方法，完成Excel读取
     *
     * @param file      Web上传Excel文件
     * @param headClass Excel表头类字节码
     * @param listener  存储Excel表中原数据与转换数据 - 监听器
     * @param <S>       从Excel文件读取的表头类字节码类型 - 原数据类型
     * @param <C>       转换后的数据类型
     * @return List<C> 监听器中存储数据，如果要直接在监听器中保存，需要开启isSave
     */
    public static <S, C> List<C> defaultRead(MultipartFile file, Class<S> headClass, AbstractExcelListener<S, C> listener) {
        AbstractExcelListener<S, C> defaultListener = listener;
        if (ObjectUtil.isNull(listener)) {
            defaultListener = new DefaultExcelListener<>();
        }
        try (InputStream inputStream = file.getInputStream()) {
            FastExcel.read(inputStream, headClass, defaultListener).sheet().doRead();
            return defaultListener.getConvertData();
        } catch (IOException e) {
            log.error("excel读取失败", e);
        }
        return null;
    }

    /**
     * 默认导出Excel，该导出模式会一次性的导出所有数据。
     * 若您需要使用该导出，请在数据量小的时候使用。
     *
     * @param response   HttpServletResponse
     * @param exportList 导出数据
     * @param fileName   导出文件名
     * @param headClass  Excel表头类字节码
     * @param sheetName  sheet名
     * @param <E>        导出数据类型
     */
    public static <E> void defaultExport(HttpServletResponse response, List<E> exportList,
                                         String fileName, Class<E> headClass, String sheetName) {
        try (OutputStream outputStream = getOutputStream(fileName, response)) {
            FastExcel.write(outputStream, headClass).sheet(sheetName).doWrite(exportList);
        } catch (IOException e) {
            log.error("excel导出失败", e);
        }
    }

    /**
     * 该导出模式会分批的导出数据，进入同一张sheet。
     *
     * @param response   HttpServletResponse
     * @param exportList 导出数据
     * @param frequency  分批数量
     * @param fileName   导出文件名
     * @param headClass  Excel表头类字节码
     * @param sheetName  sheet名
     * @param <E>        导出数据类型
     */
    public static <E> void repeatedWriteToSameSheetExport(HttpServletResponse response, List<E> exportList, int frequency,
                                                          String fileName, Class<E> headClass, String sheetName) {
        try (ExcelWriter excelWriter = FastExcel.write(getOutputStream(fileName, response), headClass).build()) {
            WriteSheet writeSheet = FastExcel.writerSheet(sheetName).build();
            for (int i = 0; i < frequency; i++) {
                excelWriter.write(exportList, writeSheet);
            }
        }
    }

    /**
     * 该导出模式会分批的导出数据，进入多张sheet。
     *
     * @param response      HttpServletResponse
     * @param exportList    导出数据
     * @param fileName      导出文件名
     * @param headClass     Excel表头类字节码
     * @param sheetName     sheet名
     * @param sheetQuantity sheet数量
     * @param <E>           导出数据类型
     */
    public static <E> void repeatedWriteToMultipleSheetExport(HttpServletResponse response, List<E> exportList,
                                                              String fileName, Class<E> headClass, String sheetName, int sheetQuantity) {
        try (ExcelWriter excelWriter = FastExcel.write(getOutputStream(fileName, response), headClass).build()) {
            for (int i = 0; i < 5; i++) {
                WriteSheet writeSheet = FastExcel.writerSheet(i, sheetName + i).build();
                excelWriter.write(exportList, writeSheet);
            }
        }
    }

    /**
     * 获取HttpServletResponse输出流，并且修改内容类型、头信息和编码。
     *
     * @param fileName 文件名
     * @param response HttpServletResponse
     * @return HttpServletResponse输出流
     */
    public static OutputStream getOutputStream(String fileName, HttpServletResponse response) {
        try {
            fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            return response.getOutputStream();
        } catch (IOException e) {
            throw new BusinessException("导出excel表格失败!");
        }
    }
}
